<?php

namespace Smalot\Cups\Transport;

use GuzzleHttp\Psr7\Uri;
use Http\Client\Common\Plugin\AddHostPlugin;
use Http\Client\Common\Plugin\ContentLengthPlugin;
use Http\Client\Common\Plugin\DecoderPlugin;
use Http\Client\Common\Plugin\ErrorPlugin;
use Http\Client\Common\PluginClient;
use Http\Client\Socket\Client as SocketHttpClient;
use Http\Client\HttpClient;
use GuzzleHttp\Psr7\HttpFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Smalot\Cups\CupsException;

/**
 * Improved Client class that properly handles IP address connections
 * and fixes the "no Host header" issue
 *
 * @package Smalot\Cups\Transport
 */
class ImprovedClient implements HttpClient
{
    private ?string $targetHost = null;
    private ?int $targetPort = null;
    private bool $useDirectConnection = false;
    private HttpClient $httpClient;
    private ?string $username = null;
    private ?string $password = null;
    private int $authType = 1; // AUTHTYPE_BASIC
    
    const AUTHTYPE_BASIC = 1;
    const AUTHTYPE_DIGEST = 2;

    /**
     * ImprovedClient constructor.
     *
     * @param string|null $username
     * @param string|null $password
     * @param array $socketClientOptions
     * @param string|null $targetHost IP address or hostname
     * @param int|null $targetPort Port number (default: 631)
     * @param bool $useDirectConnection Whether to use direct IP connection
     */
    public function __construct(
        ?string $username = null,
        ?string $password = null,
        array $socketClientOptions = [],
        ?string $targetHost = null,
        ?int $targetPort = null,
        bool $useDirectConnection = false
    ) {
        $this->username = $username;
        $this->password = $password;
        $this->targetHost = $targetHost;
        $this->targetPort = $targetPort ?? 631;
        $this->useDirectConnection = $useDirectConnection;

        // Configure socket options for direct IP connection
        if ($this->useDirectConnection && $targetHost) {
            $socketClientOptions['remote_socket'] = "tcp://{$targetHost}:{$this->targetPort}";
            $socketClientOptions['timeout'] = $socketClientOptions['timeout'] ?? 30;
        }

        // Create HTTP client
        if ($this->useDirectConnection && $targetHost) {
            $this->httpClient = $this->createDirectConnectionClient($socketClientOptions);
        } else {
            $httpFactory = new HttpFactory();
            $this->httpClient = new SocketHttpClient($httpFactory, $socketClientOptions);
        }
    }

    /**
     * Create HTTP client for direct IP connections
     *
     * @param array $socketClientOptions
     * @return PluginClient
     */
    private function createDirectConnectionClient(array $socketClientOptions): PluginClient
    {
        $httpFactory = new HttpFactory();
        $socketClient = new SocketHttpClient($httpFactory, $socketClientOptions);

        // Create proper host URI for AddHostPlugin
        $hostUri = "http://{$this->targetHost}:{$this->targetPort}";

        return new PluginClient(
            $socketClient,
            [
                new ErrorPlugin(),
                new ContentLengthPlugin(),
                new DecoderPlugin(),
                new AddHostPlugin(new Uri($hostUri)),
            ]
        );
    }

    /**
     * Enhanced sendRequest method with better error handling
     *
     * @param RequestInterface $request
     * @return ResponseInterface
     * @throws CupsException
     */
    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        // Add authentication if configured
        if ($this->username || $this->password) {
            $request = $this->addAuthentication($request);
        }

        // For direct connections, ensure proper headers are set
        if ($this->useDirectConnection && $this->targetHost) {
            $request = $this->enhanceRequestForDirectConnection($request);
        }

        try {
            return $this->httpClient->sendRequest($request);
        } catch (\Exception $e) {
            throw new CupsException(
                "Failed to send IPP request: {$e->getMessage()}",
                $e->getCode(),
                $e
            );
        }
    }

    /**
     * Add authentication headers to request
     *
     * @param RequestInterface $request
     * @return RequestInterface
     * @throws CupsException
     */
    private function addAuthentication(RequestInterface $request): RequestInterface
    {
        switch ($this->authType) {
            case self::AUTHTYPE_BASIC:
                $pass = base64_encode($this->username . ':' . $this->password);
                $authentication = 'Basic ' . $pass;
                break;

            case self::AUTHTYPE_DIGEST:
                throw new CupsException('Digest auth type not supported');

            default:
                throw new CupsException('Unknown auth type');
        }

        return $request->withHeader('Authorization', $authentication);
    }

    /**
     * Enhance request for direct IP connections
     *
     * @param RequestInterface $request
     * @return RequestInterface
     */
    private function enhanceRequestForDirectConnection(RequestInterface $request): RequestInterface
    {
        // Ensure Host header is properly set
        $hostHeader = $this->targetHost . ':' . $this->targetPort;
        $request = $request->withHeader('Host', $hostHeader);

        // Add standard IPP headers
        $request = $request
            ->withHeader('User-Agent', 'CUPS-IPP-Client/2.0')
            ->withHeader('Accept', 'application/ipp')
            ->withHeader('Accept-Language', 'en-us')
            ->withHeader('Accept-Encoding', 'gzip, deflate')
            ->withHeader('Connection', 'keep-alive');

        // Ensure Content-Type is set for IPP requests
        if (!$request->hasHeader('Content-Type')) {
            $request = $request->withHeader('Content-Type', 'application/ipp');
        }

        // Update URI to use the target host
        $uri = $request->getUri()
            ->withHost($this->targetHost)
            ->withPort($this->targetPort)
            ->withScheme('http');

        return $request->withUri($uri);
    }

    /**
     * Factory method to create client for IP address connections
     *
     * @param string $ipAddress
     * @param int $port
     * @param string|null $username
     * @param string|null $password
     * @param array $options
     * @return static
     */
    public static function createForIpAddress(
        string $ipAddress,
        int $port = 631,
        ?string $username = null,
        ?string $password = null,
        array $options = []
    ): self {
        $socketOptions = array_merge([
            'timeout' => 30,
            'stream_context_options' => [
                'socket' => [
                    'so_reuseport' => true,
                    'so_keepalive' => true,
                ]
            ]
        ], $options);

        return new self(
            $username,
            $password,
            $socketOptions,
            $ipAddress,
            $port,
            true
        );
    }

    /**
     * Test connection to the target host
     *
     * @return bool
     */
    public function testConnection(): bool
    {
        if (!$this->targetHost) {
            return false;
        }

        $connection = @fsockopen(
            $this->targetHost,
            $this->targetPort,
            $errno,
            $errstr,
            5
        );

        if ($connection) {
            fclose($connection);
            return true;
        }

        return false;
    }

    /**
     * Get connection information
     *
     * @return array
     */
    public function getConnectionInfo(): array
    {
        return [
            'target_host' => $this->targetHost,
            'target_port' => $this->targetPort,
            'use_direct_connection' => $this->useDirectConnection,
            'auth_type' => $this->authType,
            'has_credentials' => !empty($this->username) || !empty($this->password)
        ];
    }

    /**
     * Create a test request to verify IPP service
     *
     * @return RequestInterface
     */
    public function createTestRequest(): RequestInterface
    {
        $httpFactory = new HttpFactory();
        $uri = "http://{$this->targetHost}:{$this->targetPort}/ipp/print";

        $request = $httpFactory->createRequest('POST', $uri)
            ->withHeader('Content-Type', 'application/ipp')
            ->withHeader('Host', $this->targetHost . ':' . $this->targetPort)
            ->withHeader('User-Agent', 'CUPS-IPP-Client/2.0');

        // Add minimal IPP request body for testing
        $body = $httpFactory->createStream('');
        return $request->withBody($body);
    }
}